package com.example.demo.util;

import com.alibaba.fastjson.JSON;
import com.example.demo.util.converters.ConverterLoader;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * <p>
 *     用在GET请求上，组装请求对象上，spring boot 2.1以下版本，Fegin不能用对象接收参数，用request一个一个
 *     取出来又很麻烦很臃肿。利用到反射和Map接收参数，自动组装java bean属性，
 *     配合{@code NullvalueHandlerFilter}
 *     能把 {@code ?name=&age=12&sex=} 这种空参数过滤
 * </p>
 *
 *
 * @author 陈浩
 * @date 2021/1/8 10:28
 */
public class QueryBeanUtils {

    /**
     * 利用fastJson序列化
     * @param request
     * @return
     */
    public static <T> T jsonToObject(HttpServletRequest request, Class<T> t) {
        Map<String, String> parameterMapFromReq = getParameterMapFromReq(request);
        String s = JSON.toJSONString(parameterMapFromReq);
        return JSON.parseObject(s, t);
    }

    public static <T> T mapToObject(HttpServletRequest request, Class<T> t) {
        Map<String, String> parameterMapFromReq = getParameterMapFromReq(request);
        return transMap2Bean(parameterMapFromReq, t);
    }


    /**
     * 构造参数对象
     * <p>
     *     注意！！！
     *     接收的java bean不能使用 lombok的
     *     <code>
     *         @Accessors(chain = true)
     *     </code>
     *     链式对象，否则会报找不到对应属性的set方法，如要使用，请用
     *     lombok.Builder
     *      <code>
     *          @Builder
     *      </code>
     * </p>
     *
     * @see lombok.Builder
     * @see lombok.experimental.Accessors
     *
     * @param map get请求接收参数
     * @return 返回组装好的对象
     */
    private static <T> T transMap2Bean(Map<String, String> map, Class<T> t) {
        T t1 = null;
        try {
            T finalT = t1 = t.newInstance();
            Map<String, MethodField> setMethod = new HashMap<>(16);
            final Map<String, MethodField> method = getSetMethod(t, setMethod);
            map.forEach((k, v) -> {
                try {
                    MethodField methodField = method.get(k);
                    if (methodField != null) {
                        Method m = methodField.getMethod();
                        m.invoke(finalT, string2Type(v, methodField.getField()));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });

        } catch (Exception e) {
            System.out.println("transMap2Bean Error " + e);
        }
        return t1;
    }



    private static Map<String, MethodField> getSetMethod(Class clazz, Map<String, MethodField> setMethod) {
        Class superclass = clazz.getSuperclass();
        if (superclass != null) {
            try {
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    String name = declaredField.getName();
                    String setName = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
                    Method method = clazz.getMethod(setName, declaredField.getType());
                    setMethod.put(name, new MethodField(method, declaredField));
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            return getSetMethod(superclass, setMethod);
        } else {
            return setMethod;
        }
    }

    /**
     * 如有数组参数，只取第一个参数，所以不适用GET请求传数组参数
     * @param request 请求
     * @return map参数
     */
    private static Map<String, String> getParameterMapFromReq(ServletRequest request) {
        Map<String, String[]> parameterMap = new HashMap<>(request.getParameterMap());
        parameterMap.values().removeIf(v -> !StringUtils.hasText(v[0]));
        Map<String, String> param = null;
        if (!parameterMap.isEmpty()) {
            param = new HashMap<>(16);
            for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
                param.put(stringEntry.getKey(), stringEntry.getValue()[0]);
            }
        }
        return param;
    }

    /**
     * 把String转为字段对应类型
     * @param value 属性值
     * @param declaredField 字段Field，在String转换时间类型要用到它获取@DateTimeFormat的值
     * @return 转化后的值
     */
    private static Object string2Type(String value, Field declaredField) throws Exception {
        return ConverterLoader.getConverter(declaredField.getType()).convertToJavaData(value, declaredField);
    }

    static class MethodField {
        public MethodField(Method method, Field field) {
            this.method = method;
            this.field = field;
        }

        private Method method;

        private Field field;

        public Method getMethod() {
            return method;
        }

        public Field getField() {
            return field;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof MethodField)) {
                return false;
            }
            MethodField that = (MethodField) o;
            return Objects.equals(method, that.method) && Objects.equals(field, that.field);
        }

        @Override
        public int hashCode() {
            return Objects.hash(method, field);
        }
    }


}
